package mirrormonkey.rpc;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import mirrormonkey.rpc.proxy.EntityProxy;
import mirrormonkey.rpc.tools.singlecall.entities.ClientLocalInvokeRotatableBox;
import mirrormonkey.rpc.tools.singlecall.entities.LocalInvokeRotatableBox;
import mirrormonkey.rpc.tools.singlecall.entities.RotatableBox;
import mirrormonkey.rpc.tools.singlecall.entities.ServerLocalInvokeRotatableBox;
import mirrormonkey.rpc.tools.singlecall.specs.Rotatable;

import org.junit.After;
import org.junit.Test;

import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;

public class SimpleInvocationHandlerTest extends SingleClientTestProto {

	public static final class BoxTuple {

		public RotatableBox serverBox;

		public EntityProxy<Rotatable> serverProxy;

		public RotatableBox clientBox;

		public EntityProxy<Rotatable> clientProxy;

	}

	public BoxTuple jumpingBox;

	public BoxTuple[] pillarBoxes;

	public RotatableBox serverBox;

	public RotatableBox clientBox;

	public EntityProxy<Rotatable> serverProxy;

	public EntityProxy<Rotatable> clientProxy;

	@After
	@Override
	public void tearDown() throws Exception {
		super.tearDown();
		serverBox = null;
		clientBox = null;
		serverBox = null;
		clientBox = null;
		serverProxy = null;
		clientProxy = null;
	}

	public void createBox() {
		createBox(new RotatableBox(Vector3f.ZERO));
	}

	public void createBox(RotatableBox serverBox) {
		createBox(serverBox, RotatableBox.class);
	}

	public BoxTuple createBoxTuple(Vector3f position, ColorRGBA color,
			float scale) {
		BoxTuple tuple = new BoxTuple();
		tuple.serverBox = new RotatableBox(Vector3f.ZERO);
		tuple.serverBox.setLocalTranslation(position);
		tuple.serverProxy = serverApplication.rpcModule
				.createRpcProxy(Rotatable.class);
		tuple.serverProxy.addTargetEntity(tuple.serverBox);
		tuple.serverProxy.addMessageConnection(getFirstConnection());
		serverApplication.coreModule.addMapping(getFirstConnection(),
				tuple.serverBox, RotatableBox.class,
				tuple.serverBox.getLocalTranslation());

		sleepNoInterrupt(100);
		processEvents();

		tuple.clientBox = (RotatableBox) clientApplication.appState.entityProvider
				.getData(tuple.serverBox.getData().id).entity;
		tuple.clientBox.setVisibilityListener(visibilityListener);
		tuple.clientProxy = clientApplication.rpcModule
				.createRpcProxy(Rotatable.class);
		tuple.serverProxy.getCallTarget().setLoadParams(color, scale, position);
		return tuple;
	}

	public void createJumpingBox() {
		jumpingBox = createBoxTuple(Vector3f.ZERO, ColorRGBA.Red, 1.5f);
	}

	public void createPillarBoxes() {
		pillarBoxes = new BoxTuple[] {
				createBoxTuple(new Vector3f(0f, 2f, 0f), ColorRGBA.Blue, 1f),
				createBoxTuple(new Vector3f(0f, -2f, 0f), ColorRGBA.Blue, 1f) };
	}

	public void createBox(RotatableBox serverBox,
			Class<? extends Rotatable> clientClass) {
		this.serverBox = serverBox;
		serverProxy = serverApplication.rpcModule
				.createRpcProxy(Rotatable.class);
		serverProxy.addTargetEntity(serverBox);
		serverProxy.addMessageConnection(getFirstConnection());
		serverApplication.coreModule.addMapping(getFirstConnection(),
				serverBox, clientClass, serverBox.getLocalTranslation());

		sleepNoInterrupt(100);
		processEvents();

		clientBox = (RotatableBox) clientApplication.appState.entityProvider
				.getData(serverBox.getData().id).entity;
		clientProxy = clientApplication.rpcModule
				.createRpcProxy(Rotatable.class);
		clientProxy.addTargetEntity(clientBox);
		clientProxy.addMessageConnection(clientApplication.client);
	}

	@Test
	public void testSingleEntityCall() {
		sleepNoInterrupt(1000);
		createBox();

		Vector3f axis = new Vector3f(0f, 1f, 0.5f);

		long lastTime = System.currentTimeMillis();
		float rotSpeed = (float) ((2 * Math.PI) / 1000);

		long time = 0;
		while (time <= 5000) {
			long delta = System.currentTimeMillis() - lastTime;
			time += delta;
			lastTime = System.currentTimeMillis();
			Quaternion serverRot = serverBox.getLocalRotation().clone();

			Quaternion newRot = serverRot.clone();
			newRot = newRot.fromAngleAxis(
					(float) ((serverRot.toAngleAxis(new Vector3f()) + delta
							* rotSpeed) % (2 * Math.PI)), axis);

			serverProxy.getCallTarget().setRotation(newRot);

			sleepNoInterrupt(100);
			processEvents();

			assertTrue(serverBox.getLocalRotation().equals(serverRot));
			assertTrue(clientBox.getLocalRotation().equals(newRot));

			clientProxy.getCallTarget().setRotation(newRot);

			sleepNoInterrupt(100);
			processEvents();

			assertTrue(serverBox.getLocalRotation().equals(newRot));
			assertTrue(clientBox.getLocalRotation().equals(newRot));
		}

		serverApplication.coreModule.removeMapping(getFirstConnection(),
				serverBox);
		sleepNoInterrupt(1000);
	}

	@Test
	public void testLocalInvoke() {
		sleepNoInterrupt(1000);
		createBox(new LocalInvokeRotatableBox(Vector3f.ZERO));

		Vector3f axis = new Vector3f(0f, 1f, 0.5f);

		long lastTime = System.currentTimeMillis();
		float rotSpeed = (float) ((2 * Math.PI) / 1000);

		long time = 0;
		while (time <= 5000) {
			long delta = System.currentTimeMillis() - lastTime;
			time += delta;
			lastTime = System.currentTimeMillis();
			Quaternion serverRot = serverBox.getLocalRotation().clone();

			Quaternion newRot = serverRot.clone();
			newRot = newRot.fromAngleAxis(
					(float) ((serverRot.toAngleAxis(new Vector3f()) + delta
							* rotSpeed) % (2 * Math.PI)), axis);

			serverProxy.getCallTarget().setRotation(newRot);
			sleepNoInterrupt(10);

			processEvents();

			assertTrue(serverBox.getLocalRotation().equals(newRot));
			assertTrue(clientBox.getLocalRotation().equals(newRot));
		}

		serverApplication.coreModule.removeMapping(getFirstConnection(),
				serverBox);
		sleepNoInterrupt(1000);
	}

	@Test
	public void testAllowedCall() {
		sleepNoInterrupt(1000);
		createBox(new ServerLocalInvokeRotatableBox(Vector3f.ZERO),
				ClientLocalInvokeRotatableBox.class);

		Vector3f axis = new Vector3f(0f, 1f, 0.5f);

		long lastTime = System.currentTimeMillis();
		float rotSpeed = (float) ((2 * Math.PI) / 1000);

		long time = 0;
		while (time <= 5000) {
			long delta = System.currentTimeMillis() - lastTime;
			time += delta;
			lastTime = System.currentTimeMillis();
			Quaternion serverRot = serverBox.getLocalRotation().clone();

			Quaternion newRot = serverRot.clone();
			newRot = newRot.fromAngleAxis(
					(float) ((serverRot.toAngleAxis(new Vector3f()) + delta
							* rotSpeed) % (2 * Math.PI)), axis);

			serverProxy.getCallTarget().setRotation(newRot);
			sleepNoInterrupt(10);

			processEvents();

			assertTrue(serverBox.getLocalRotation().equals(newRot));
			assertTrue(clientBox.getLocalRotation().equals(newRot));
		}

		serverApplication.coreModule.removeMapping(getFirstConnection(),
				serverBox);
		sleepNoInterrupt(1000);
	}

	@Test
	public void testDisallowedCall() {
		sleepNoInterrupt(1000);
		createBox(new ClientLocalInvokeRotatableBox(Vector3f.ZERO),
				ServerLocalInvokeRotatableBox.class);

		Vector3f axis = new Vector3f(0f, 1f, 0.5f);

		long lastTime = System.currentTimeMillis();
		float rotSpeed = (float) ((2 * Math.PI) / 1000);

		long time = 0;
		while (time <= 5000) {
			long delta = System.currentTimeMillis() - lastTime;
			time += delta;
			lastTime = System.currentTimeMillis();
			Quaternion serverRot = serverBox.getLocalRotation().clone();
			Quaternion clientRot = clientBox.getLocalRotation().clone();

			Quaternion newRot = serverRot.clone();
			newRot = newRot.fromAngleAxis(
					(float) ((serverRot.toAngleAxis(new Vector3f()) + delta
							* rotSpeed) % (2 * Math.PI)), axis);

			serverProxy.getCallTarget().setRotation(newRot);
			sleepNoInterrupt(10);

			processEvents();

			assertTrue(serverBox.getLocalRotation().equals(newRot));
			assertTrue(clientBox.getLocalRotation().equals(clientRot));
		}

		serverApplication.coreModule.removeMapping(getFirstConnection(),
				serverBox);
		sleepNoInterrupt(1000);
	}

	@Test
	public void testNullArgs() {
		sleepNoInterrupt(1000);
		createBox();

		assertNotNull(clientBox.nullSet);

		serverProxy.getCallTarget().setNullObject(null);

		sleepNoInterrupt(100);
		processEvents();

		assertNull(clientBox.nullSet);

		sleepNoInterrupt(1000);
	}

	@Test
	public void testUnreliableCall() {
		sleepNoInterrupt(1000);
		createBox();

		assertNotNull(clientBox.unreliableNullSet);

		serverProxy.getCallTarget().unreliableNullCall(null);

		sleepNoInterrupt(100);
		processEvents();

		assertNull(clientBox.unreliableNullSet);

		sleepNoInterrupt(1000);
	}

	@Test
	public void testIdentityAwareArgument() {
		sleepNoInterrupt(1000);
		createJumpingBox();
		createPillarBoxes();

		for (int i = 0; i < 10; i++) {
			int pillarId = i % 2;
			jumpingBox.serverProxy.getCallTarget().idAwareCall(
					pillarBoxes[pillarId].serverBox);

			sleepNoInterrupt(300);
			processEvents();

			assertSame(pillarBoxes[pillarId].clientBox,
					jumpingBox.clientBox.setByCall);
			assertEquals(pillarBoxes[pillarId].serverBox.getLocalTranslation(),
					jumpingBox.clientBox.getLocalTranslation());
		}

		sleepNoInterrupt(1000);
	}

	@Test
	public void testInterfaceBasedIdentityAwareCall() {
		sleepNoInterrupt(1000);
		createJumpingBox();
		createPillarBoxes();

		for (int i = 0; i < 10; i++) {
			int pillarId = i % 2;
			jumpingBox.serverProxy.getCallTarget().interfaceBasedIdAwareCall(
					pillarBoxes[pillarId].serverBox);

			sleepNoInterrupt(300);
			processEvents();

			assertSame(pillarBoxes[pillarId].clientBox,
					jumpingBox.clientBox.setByCall);
			assertEquals(pillarBoxes[pillarId].serverBox.getLocalTranslation(),
					jumpingBox.clientBox.getLocalTranslation());
		}

		sleepNoInterrupt(1000);
	}

	@Test
	public void testUnknownIdentityAwareArgument() {
		sleepNoInterrupt(1000);
		createJumpingBox();
		RotatableBox box = new RotatableBox(Vector3f.ZERO);

		jumpingBox.serverProxy.getCallTarget().idAwareCall(box);

		sleepNoInterrupt(300);
		processEvents();

		assertNull(jumpingBox.clientBox.setByCall);

		sleepNoInterrupt(1000);
	}

	@Test
	public void testNullIdentityAwareArgument() {
		sleepNoInterrupt(1000);
		createJumpingBox();

		jumpingBox.serverProxy.getCallTarget().idAwareCall(null);

		sleepNoInterrupt(300);
		processEvents();

		assertNull(jumpingBox.clientBox.nulledByCall);

		sleepNoInterrupt(1000);
	}

}
